package com.tiennt.android.droidcamreader;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.FileUtils;

import android.app.ProgressDialog;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.util.Log;

import com.googlecode.tesseract.android.TessBaseAPI;

public class OcrInitAsyncTask extends AsyncTask<Void, String, Boolean> {
	public static final String PRE_URL = "https://tesseract-ocr.googlecode.com/files/";
	public static final String PRE_DOWNLOAD_FILE_NAME = "tesseract-ocr-3.02.";
	public static final String DOWNLOAD_FILE_EXTENSION = ".tar.gz";
	public static final String DOWNLOAD_FOLDER_NAME = "download";
	public static final String TAG = "OcrEngineInitAsyncTask";
	public static final String INSTALLED_FILE_EXTENSION = ".traineddata";
	public static final String INSTALL_FOLDER_NAME = "tessdata";

	public String INSTALL_FOLDER_PATH;

	ProgressDialog initOcrProgressDialog;
	ProgressDialog installOcrProgressDialog;

	CameraActivity mCamActivity;
	CameraActivityHandler mHandler;

	String rootDir;
	String downloadFileName;

	String langCode;
	String langName;

	TessBaseAPI baseAPI;

	public OcrInitAsyncTask(CameraActivity captureActivity,
			ProgressDialog dialog, String fileDir, String langCode) {
		this.initOcrProgressDialog = dialog;
		this.mCamActivity = captureActivity;
		this.rootDir = fileDir;
		this.langCode = langCode;

		downloadFileName = PRE_DOWNLOAD_FILE_NAME + langCode
				+ DOWNLOAD_FILE_EXTENSION;

		INSTALL_FOLDER_PATH = rootDir + File.separator + INSTALL_FOLDER_NAME;

		langName = LanguageCodeHelper
				.getOcrLanguageName(mCamActivity, langCode);
		mHandler = mCamActivity.getHandler();
	}

	@Override
	protected void onPreExecute() {
		// TODO Auto-generated method stub
		super.onPreExecute();
		installOcrProgressDialog = new ProgressDialog(mCamActivity);
		installOcrProgressDialog.setTitle("Please wait");
		installOcrProgressDialog
				.setMessage("Checking for data installation...");
		installOcrProgressDialog.setIndeterminate(false);
		installOcrProgressDialog
				.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		installOcrProgressDialog.setCancelable(false);
		installOcrProgressDialog.show();
	}

	@Override
	protected Boolean doInBackground(Void... params) {
		// TODO Auto-generated method stub
		String downloadUrl = PRE_URL + downloadFileName;
		try {
			if (!isInstalled()) {
				if (!mCamActivity.isNetworkConnected()) {
					initOcrProgressDialog.dismiss();
					mHandler.sendEmptyMessage(CameraActivityHandler.NO_INTERNET_CONNECTION);
					return false;
				}
				File downloadedFile = downloadFile(downloadUrl, rootDir);
				File unGzipFile = unGzip(downloadedFile);
				File unTarFile = unTar(unGzipFile);
				installDataForAllEngines(unTarFile);
			}

			installOcrProgressDialog.dismiss();

			if (initEngineWithOneLanguage()) {
				mCamActivity.setBaseApi(baseAPI);
				return true;
			}
		} catch (Exception ex) {
			installOcrProgressDialog.dismiss();
			mHandler.obtainMessage(
					CameraActivityHandler.MSG_WCODE_OCR_INIT_FAILED,
					ex.getMessage());
			ex.printStackTrace();
			Log.d(TAG, ex.getMessage());
		}
		return false;
	}

	public File downloadFile(String downloadUrl, String rootDir)
			throws Exception {

		URL url = new URL(downloadUrl);
		URLConnection connection = url.openConnection();
		boolean connectOk = false;
		publishProgress("Connecting to data server...");
		try {
			connection.connect();
			connectOk = true;
		} catch (IOException ex) {
			// Cannot connect to server. Any connection error?

			Log.v(TAG, "Cannot connect: " + ex.getMessage());
			throw new Exception("Cannot connect to download server.");
		}

		boolean prepareOk = false;
		if (connectOk) {
			int fileLength = connection.getContentLength();
			String filePath = "";
			// Prepare for downloading.
			String downloadFolder = rootDir + File.separator
					+ DOWNLOAD_FOLDER_NAME + File.separator;
			filePath = downloadFolder + downloadFileName;

			File downloadFolderFile = new File(downloadFolder);
			if (!downloadFolderFile.exists()) {
				if (!downloadFolderFile.mkdirs()) {
					Log.v(TAG, "Cannot create directory.");
					throw new Exception(
							"Cannot create directory for storing download data.");
				} else {

					prepareOk = true;
				}
			} else {
				// Delete file.
				File f = new File(filePath);
				if (f.exists()) {
					if (f.length() == fileLength) {
						return f;
					} else {
						f.delete();
					}
				}
				// Done
				prepareOk = true;
			}

			if (prepareOk) {
				try {
					InputStream input = new BufferedInputStream(
							url.openStream());
					OutputStream output = new FileOutputStream(new File(
							filePath));

					byte data[] = new byte[1024];
					long downloaded = 0;
					int count;
					while ((count = input.read(data)) != -1) {
						downloaded += count;

						// publishing the progress....
						publishProgress("Downloading data for language "
								+ langName + "...",
								(int) (downloaded * 100 / fileLength) + "");
						output.write(data, 0, count);
					}

					output.flush();
					output.close();
					input.close();

					return new File(filePath);
				} catch (IOException ex) {
					Log.v(TAG, "Cannot download." + ex.getMessage());
					throw new Exception("Cannot download required data.");
				}
			}
		}
		return null;
	}

	public File unGzip(File gzFile) throws Exception {
		publishProgress("Extracting package...", "0");
		int uncompressedFileSize = getGzipSizeUncompressed(gzFile);
		String parentFolder = gzFile.getParent();
		String outputFileName = PRE_DOWNLOAD_FILE_NAME + langCode + ".tar";

		File unzippedTar = new File(parentFolder + File.separator
				+ outputFileName);
		if (unzippedTar.exists()) {
			if (unzippedTar.length() == uncompressedFileSize) {
				gzFile.delete();
				return unzippedTar;
			} else {
				unzippedTar.delete();
			}
		}

		// Un gz
		GZIPInputStream gzipInputStream = new GZIPInputStream(
				new BufferedInputStream(new FileInputStream(gzFile)));
		OutputStream outputStream = new FileOutputStream(unzippedTar);
		BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
				outputStream);

		final int BUFFER = 8192;
		byte[] data = new byte[BUFFER];
		int unzippedBytes = 0;
		int percentComplete = 0;
		int len;
		while ((len = gzipInputStream.read(data, 0, BUFFER)) > 0) {
			bufferedOutputStream.write(data, 0, len);
			unzippedBytes += len;
			percentComplete = (int) ((unzippedBytes / (float) uncompressedFileSize) * 50);
			publishProgress("Extracting package...", percentComplete + "");
		}

		gzipInputStream.close();
		bufferedOutputStream.flush();
		bufferedOutputStream.close();

		gzFile.delete();
		return unzippedTar;
	}

	private int getGzipSizeUncompressed(File zipFile) throws IOException {
		RandomAccessFile raf = new RandomAccessFile(zipFile, "r");
		raf.seek(raf.length() - 4);
		int b4 = raf.read();
		int b3 = raf.read();
		int b2 = raf.read();
		int b1 = raf.read();
		raf.close();
		return (b1 << 24) | (b2 << 16) + (b3 << 8) + b4;
	}

	public File unTar(File tarFile) throws Exception {
		int sizeUncompressed = getTarUncompressedSize(tarFile);

		String parentFolder = tarFile.getParent();
		String outputFolderName = PRE_DOWNLOAD_FILE_NAME + langCode;

		File unzippedTarFolder = new File(parentFolder + File.separator
				+ outputFolderName + File.separator);
		if (unzippedTarFolder.exists()) {
			FileUtils.cleanDirectory(unzippedTarFolder);
		} else {
			unzippedTarFolder.mkdirs();
		}

		final int BUFFER = 8192;
		int unzippedBytes = 0;
		int percentComplete = 0;

		FileInputStream fin = new FileInputStream(tarFile);
		TarArchiveInputStream tarIn = new TarArchiveInputStream(fin);

		String outputPath = parentFolder + File.separator + outputFolderName
				+ File.separator;

		TarArchiveEntry entry = null;

		/** Read the tar entries using the getNextEntry method **/

		while ((entry = (TarArchiveEntry) tarIn.getNextEntry()) != null) {
			if (entry.isDirectory()) {

				File f = new File(outputPath + entry.getName());
				f.mkdirs();
			}
			/**
			 * If the entry is a file,write the decompressed file to the disk
			 * and close destination stream.
			 **/
			else {
				int count;
				byte data[] = new byte[BUFFER];
				String pathName = entry.getName();

				String entryName = pathName.substring(
						pathName.lastIndexOf('/') + 1, pathName.length());
				FileOutputStream fos = new FileOutputStream(outputPath
						+ entryName);
				BufferedOutputStream dest = new BufferedOutputStream(fos,
						BUFFER);
				while ((count = tarIn.read(data, 0, BUFFER)) != -1) {
					dest.write(data, 0, count);
					unzippedBytes += count;
					percentComplete = 50 + (int) ((unzippedBytes / (float) sizeUncompressed) * 50);
					publishProgress("Extracting package...", percentComplete
							+ "");
				}
				dest.close();
			}
		}

		/** Close the input stream **/

		tarIn.close();
		tarFile.delete();
		return unzippedTarFolder;
	}

	private int getTarUncompressedSize(File tarFile) throws IOException {
		int size = 0;
		TarArchiveInputStream inputStream = new TarArchiveInputStream(
				new FileInputStream(tarFile));
		TarArchiveEntry entry = null;
		while ((entry = inputStream.getNextTarEntry()) != null) {
			if (!entry.isDirectory()) {
				size += entry.getSize();
			}
		}
		inputStream.close();
		return size;
	}

	public void installDataForAllEngines(File from) throws Exception {
		publishProgress("Installing data for language " + langName + "...", "0");

		int totalSize = (int) FileUtils.sizeOfDirectory(from);
		final int BUFFER = 8192;
		int byteCount = 0;
		int percentComplete = 0;

		File tessDataFolder = new File(INSTALL_FOLDER_PATH);
		if (!tessDataFolder.exists()) {
			tessDataFolder.mkdirs();
		}

		for (File file : from.listFiles()) {
			BufferedInputStream buf = new BufferedInputStream(
					new FileInputStream(file));

			FileOutputStream fout = new FileOutputStream(new File(
					INSTALL_FOLDER_PATH + File.separator + file.getName()));

			int count = 0;

			byte data[] = new byte[BUFFER];
			while ((count = buf.read(data, 0, BUFFER)) != -1) {
				fout.write(data, 0, BUFFER);
				byteCount += count;
				percentComplete = (int) ((byteCount / (float) totalSize) * 100);
				publishProgress("Installing data for language " + langName
						+ "...", percentComplete + "");
			}
			fout.flush();
			fout.close();
			buf.close();

			FileUtils.cleanDirectory(from);
			from.delete();
		}
	}

	public void installOnlyDataForTessEngine(File from) throws Exception {
		publishProgress("Installing data for language " + langName + "...", "0");

		final int BUFFER = 8192;
		int byteCount = 0;
		int percentComplete = 0;

		File tessDataFolder = new File(INSTALL_FOLDER_PATH);
		if (!tessDataFolder.exists()) {
			tessDataFolder.mkdirs();
		}

		String lookingForFileName = langCode + INSTALLED_FILE_EXTENSION;

		File requiredFile = new File(from.getAbsolutePath() + File.separator
				+ lookingForFileName);

		if (!requiredFile.exists()) {
			throw new Exception("Required file not found.");
		}

		BufferedInputStream buf = new BufferedInputStream(new FileInputStream(
				requiredFile));

		int totalSize = (int) requiredFile.length();

		FileOutputStream fout = new FileOutputStream(new File(
				INSTALL_FOLDER_PATH + File.separator + lookingForFileName));

		int count = 0;

		byte data[] = new byte[BUFFER];
		while ((count = buf.read(data, 0, BUFFER)) != -1) {
			fout.write(data, 0, BUFFER);
			byteCount += count;
			percentComplete = (int) ((byteCount / (float) totalSize) * 100);
			publishProgress("Installing data for language " + langName + "...",
					percentComplete + "");
		}
		fout.flush();
		fout.close();
		buf.close();

		FileUtils.cleanDirectory(from);
		from.delete();
	}

	public void installFromAssets() throws Exception {

		AssetManager assetManager = mCamActivity.getAssets();
		String[] files = null;
		files = assetManager.list("");

		for (String filename : files) {
			publishProgress("Copying " + filename, "0");
			InputStream in = null;
			OutputStream out = null;
			File file = new File(filename);
			int length = (int) file.length();

			in = assetManager.open(filename);

			File outFile = new File(INSTALL_FOLDER_PATH, filename);
			out = new FileOutputStream(outFile);

			int byteCount = 0;
			int percentComplete = 0;
			byte[] buffer = new byte[8192];
			int read;

			while ((read = in.read(buffer)) != -1) {
				byteCount += read;
				percentComplete = 100 * byteCount / length;
				publishProgress("Copying " + filename, percentComplete + "");
				out.write(buffer, 0, read);
			}

			in.close();
			out.flush();
			out.close();
		}
	}

	boolean isInstalled() {
		String fileName = langCode + INSTALLED_FILE_EXTENSION;
		File requiredFile = new File(INSTALL_FOLDER_PATH + File.separator
				+ fileName);
		return requiredFile.exists();
	}

	public boolean initEngineWithAllAvailableLanguages() {
		if (baseAPI == null) {
			baseAPI = new TessBaseAPI();
		}
		baseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO_OSD);
		baseAPI.setDebug(true);
		File tessDataFolder = new File(rootDir + File.separator
				+ INSTALL_FOLDER_NAME);
		File[] tessDataFile = tessDataFolder.listFiles(new FilenameFilter() {

			@Override
			public boolean accept(File dir, String filename) {
				// TODO Auto-generated method stub
				if (filename.contains(INSTALLED_FILE_EXTENSION))
					return true;
				return false;
			}
		});

		String langToInit = "";
		for (File f : tessDataFile) {
			String fName = f.getName();
			langToInit += fName.substring(0, fName.indexOf(".")) + "+";
		}
		if (langToInit.lastIndexOf("+") == langToInit.length() - 1) {
			langToInit = langToInit.substring(0, langToInit.length() - 1);
		}
		return baseAPI.init(rootDir + File.separator, langToInit);
	}

	public boolean initEngineWithOneLanguage() throws Exception {
		if (baseAPI == null) {
			baseAPI = new TessBaseAPI();
			baseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO_OSD);
			baseAPI.setDebug(true);
			if (langCode.equals("eng")) {
				baseAPI.setVariable(
						TessBaseAPI.VAR_CHAR_WHITELIST,
						"!?@#$%&*()<>_-+=/.,:;'\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
			}
		}
		return baseAPI.init(rootDir + File.separator, langCode);
	}

	@Override
	protected void onProgressUpdate(String... values) {
		// TODO Auto-generated method stub
		super.onProgressUpdate(values);
		installOcrProgressDialog.setMessage(values[0]);

		if (values.length > 1) {
			int percent = Integer.parseInt(values[1]);
			installOcrProgressDialog.setProgress(percent);
		}
		installOcrProgressDialog.show();
	}

	@Override
	protected void onPostExecute(Boolean result) {
		// TODO Auto-generated method stub
		super.onPostExecute(result);
		try {
			initOcrProgressDialog.dismiss();
		} catch (Exception ex) {

		}
		if (result == false) {
			mHandler.obtainMessage(
					CameraActivityHandler.MSG_WCODE_OCR_INIT_FAILED,
					"Unknown reason.").sendToTarget();
			return;
		}
		mHandler.sendEmptyMessage(CameraActivityHandler.MSG_WCODE_OCR_INIT_SUCCESS);
	}
}
